package com.nulldev.util.web.HttpClient.clients.legacy;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import com.nulldev.util.concurrency.threadIt.v4.emapi.cntft.ControllableFutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.throttling.BandwidthThrottler;
import com.nulldev.util.web.HttpClient.HttpClient;
import com.nulldev.util.web.HttpClient.HttpMethod;
import com.nulldev.util.web.HttpClient.HttpRequest;
import com.nulldev.util.web.HttpClient.HttpResponse;
import com.nulldev.util.web.HttpClient.ResponseFormat;
import com.nulldev.util.web.HttpClient.contentGen.GeneratableContent;
import com.nulldev.util.web.HttpClient.util.HttpClientUtil;
import com.nulldev.util.web.HttpClient.util.HttpStreamUtil;
import com.nulldev.util.web.URLUtils.Entry;
import com.nulldev.util.web.URLUtils.URLUtils;

public abstract class LegacyHttpRequest<T> implements HttpRequest<T> {

	private final AtomicReference<HttpURLConnection> conn = new AtomicReference<HttpURLConnection>();

	@Override
	public HttpRequest<T> url(String url) throws Exception {
		this.conn.set((HttpURLConnection) new URL(url).openConnection(this.proxy));
		this.conn.get().setUseCaches(true);
		this.header("User-Agent", com.nulldev.util.web.HttpClient.HttpClient.USER_AGENT);
		this.header("Accept-Encoding", HttpStreamUtil.getSupportedCompressionString());
		return this;
	}

	@Override
	public HttpRequest<T> url(final URL url) throws Exception {
		this.conn.set((HttpURLConnection) url.openConnection(this.proxy)); // new Proxy(Type.HTTP, new InetSocketAddress("0.0.0.0", 8888)));
		this.header("User-Agent", com.nulldev.util.web.HttpClient.HttpClient.USER_AGENT);
		this.header("Accept-Encoding", HttpStreamUtil.getSupportedCompressionString());
		return this;
	}

	@Override
	public HttpRequest<T> header(String key, String value) {
		if (key == null || value == null || key.isEmpty() || value.isEmpty()) {
			return this;
		}
		if (this.conn.get() == null) {
			return this;
		}
		this.conn.get().setRequestProperty(key, value);
		return this;
	}

	@Override
	public HttpRequest<T> header(final Entry entry) {
		if (this.conn.get() == null)
			return this;
		if (entry == null || !entry.isValid())
			return this;
		this.conn.get().setRequestProperty(entry.getKey(), entry.getValue());
		return this;
	}

	@Override
	public HttpRequest<T> headers(final Entry[] entries) {
		if (entries == null || entries.length == 0) {
			return this;
		}
		for (final Entry entry : entries) {
			if (entry == null || !entry.isValid()) {
				continue;
			}
			this.conn.get().setRequestProperty(entry.getKey(), entry.getValue());
		}
		return this;
	}

	@Override
	public HttpRequest<T> forceHeader(String key, String value) {
		return this.header(key, value);
	}

	@Override
	public HttpRequest<T> timeout(Duration duration) {
		if (duration == null)
			return this;
		this.conn.get().setConnectTimeout((int) duration.toMillis());
		this.conn.get().setReadTimeout((int) duration.toMillis());
		return this;
	}

	@Override
	public HttpResponse<T> create(final HttpMethod method, final Object body) throws Exception {
		final ResponseFormat format = this.getFormat();
		if (this.conn.get() == null)
			throw new NullPointerException("conn == null!");
		this.conn.get().setRequestMethod(method.value());
		OutputStream os = null;
		if (body == null || method == HttpMethod.GET) {
		} else {
			InputStream x = null;
			if (body instanceof InputStream) {
				x = (InputStream) body;
				this.conn.get().setRequestProperty("Content-Length", String.valueOf(x.available()));
			} else if (body instanceof byte[]) {
				x = new ByteArrayInputStream((byte[]) body);
				this.conn.get().setRequestProperty("Content-Length", String.valueOf(((byte[]) body).length));
			} else if (body instanceof String) {
				x = new ByteArrayInputStream(((String) body).getBytes());
				this.conn.get().setRequestProperty("Content-Length", String.valueOf(((String) body).length()));
			} else if (body instanceof GeneratableContent) {
				x = new ByteArrayInputStream(((GeneratableContent) body).make().getBytes());
				this.conn.get().setRequestProperty("Content-Length", String.valueOf(((GeneratableContent) body).getContentLength()));
				((GeneratableContent) body).reset();
			} else {
				throw new UnsupportedOperationException("Unsupported body type: " + body);
			}
			this.conn.get().setDoOutput(true);
			os = this.conn.get().getOutputStream();
			IOUtils.autoCopy(x, os);
			IOUtils.closeQuietly(x);
			this.conn.get().getOutputStream().flush();
		}
		if (this.spoofChromium) {
			this.header("Sec-Ch-Ua", URLUtils.makeChromiumUAHeader());
			this.header("Sec-Ch-Ua-Mobile", "?0");
			this.header("Sec-Ch-Ua-Platform", "\"Windows\"");
			this.header("Sec-Fetch-User", "?1");
		}
		this.conn.get().connect();
		final InputStream is = HttpClientUtil.isSuccessfulRequest(this.conn.get().getResponseCode()) ? this.conn.get().getErrorStream()
				: this.conn.get().getInputStream();
		return new LegacyHttpResponse<T>(this.conn.get(), (bufferSize > 0 ? new BufferedInputStream(is, bufferSize) : is), os, format, this.throttler);
	}

	@Override
	public Executable<HttpResponse<T>> createTask(final HttpMethod method, final Object body) throws Exception {
		final ControllableFutureTask<HttpResponse<T>> r = new ControllableFutureTask<HttpResponse<T>>();
		HttpClient.gld.asyncSubmit(() -> {
			try {
				final LegacyHttpResponse<T> lhr = (LegacyHttpResponse<T>) create(method, body);
				if (!r.complete(lhr)) {
					IOUtils.closeQuietly(lhr);
				}
			} catch (Exception e) {
				r.fail(e);
			}
		});
		return r;
	}

	@Override
	public Executable<HttpResponse<T>> createTaskAndRun(HttpMethod method, Object body) throws Exception {
		return this.createTask(method, body);
	}

	@Override
	public boolean isSupportedMethod(HttpMethod method) {
		return method == HttpMethod.GET || method == HttpMethod.POST || method == HttpMethod.HEAD || method == HttpMethod.OPTIONS;
	}

	@Override
	public abstract ResponseFormat getFormat();

	private volatile int bufferSize = -1;
	private volatile Proxy proxy = Proxy.NO_PROXY;

	@Override
	public HttpRequest<T> proxy(Proxy proxy) {
		if (proxy != null)
			this.proxy = proxy;
		return this;
	}

	@Override
	public HttpRequest<T> enableBuffering(int bufferSize) {
		if (bufferSize <= 0)
			return this;
		this.bufferSize = bufferSize;
		return this;
	}

	private BandwidthThrottler throttler;
	private boolean spoofChromium;

	@Override
	public Object getFeatureFlag(final int feature) {
		switch (feature) {
			case FEATURE_FLAG_ENABLE_DOWNSTREAM_THROTTLING:
				return this.throttler != null ? this.throttler : HTTP_CLIENT_TRUE;
			case FEATURE_FLAG_SPOOF_CHROMIUM:
				return this.spoofChromium;
			default:
				return HTTP_CLIENT_NOT_SUPPORTED;
		}
	}

	@Override
	public boolean isFeatureFlagSupported(final int feature) {
		switch (feature) {
			case FEATURE_FLAG_ENABLE_DOWNSTREAM_THROTTLING:
			case FEATURE_FLAG_SPOOF_CHROMIUM:
			case FEATURE_FLAG_REQUEST_READ_API:
				return true;
			default:
				return false;
		}
	}

	@Override
	public HttpRequest<T> tryFeatureFlag(final int feature, final Object value) {
		if (value == null || !this.isFeatureFlagSupported(feature))
			return this;
		switch (feature) {
			case FEATURE_FLAG_ENABLE_DOWNSTREAM_THROTTLING: {
				if (!(value instanceof BandwidthThrottler))
					break;
				this.throttler = (BandwidthThrottler) value;
				break;
			}
			case FEATURE_FLAG_SPOOF_CHROMIUM:
				this.spoofChromium = this._value_asBoolean(value);
				break;
			default:
				break;
		}
		return this;
	}

	/* Read API */

	@Override
	public String url() {
		return this.conn.get().getURL().toString();
	}

	@Override
	public Map<String, List<String>> headers() {
		return this.conn.get().getRequestProperties();
	}
}
